Passed
Push — task/manager-application-revie... ( a77584...4e1025 )
by Yonathan
04:08
created

applicationHooks.tsx ➔ useFetchJob   A

Complexity

Conditions 3

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 21
rs 9.7
c 0
b 0
f 0
cc 3
1
/* eslint-disable camelcase */
2
import { useCallback, useEffect, useState } from "react";
3
import { useSelector } from "react-redux";
4
import { DispatchType } from "../configureStore";
5
import { RootState } from "../store/store";
6
import { getAwardRecipientTypes as fetchAwardRecipientTypes } from "../store/AwardRecipientType/awardRecipientTypeActions";
7
import { getAwardRecipientTypes } from "../store/AwardRecipientType/awardRecipientTypeSelector";
8
import { getAwardRecognitionTypes as fetchAwardRecognitionTypes } from "../store/AwardRecognitionType/awardRecognitionTypeActions";
9
import { getAwardRecognitionTypes } from "../store/AwardRecognitionType/awardRecognitionTypeSelector";
10
import { getEducationTypes as fetchEducationTypes } from "../store/EducationType/educationTypeActions";
11
import { getEducationTypes } from "../store/EducationType/educationTypeSelector";
12
import { getEducationStatuses as fetchEducationStatuses } from "../store/EducationStatus/educationStatusActions";
13
import { getEducationStatuses } from "../store/EducationStatus/educationStatusSelector";
14
import {
15
  AwardRecipientType,
16
  AwardRecognitionType,
17
  EducationType,
18
  EducationStatus,
19
  Job,
20
  Experience as ExperienceType,
21
  Skill,
22
  ExperienceSkill,
23
  ApplicationNormalized,
24
  Criteria,
25
  JobPosterQuestion,
26
  JobApplicationAnswer,
27
  Application,
28
  User,
29
} from "../models/types";
30
import {
31
  getApplicationById,
32
  getApplicationIsUpdating,
33
  getApplicationNormalized,
34
  getJobApplicationAnswers,
35
} from "../store/Application/applicationSelector";
36
import {
37
  fetchApplication,
38
  fetchApplicationNormalized,
39
} from "../store/Application/applicationActions";
40
import {
41
  getCriteriaByJob,
42
  getJob,
43
  getJobIsUpdating,
44
  getJobPosterQuestionsByJob,
45
} from "../store/Job/jobSelector";
46
import { fetchJob } from "../store/Job/jobActions";
47
import { ApplicationStatusId } from "../models/lookupConstants";
48
import {
49
  getExperienceByApplicant,
50
  getExperienceByApplication,
51
  getExperienceSkillsByApplicant,
52
  getExperienceSkillsByApplication,
53
  getUpdatingByApplicant,
54
  getUpdatingByApplication,
55
} from "../store/Experience/experienceSelector";
56
import {
57
  fetchExperienceByApplicant,
58
  fetchExperienceByApplication,
59
} from "../store/Experience/experienceActions";
60
import { getSkills, getSkillsUpdating } from "../store/Skill/skillSelector";
61
import { fetchSkills } from "../store/Skill/skillActions";
62
import { getUserById } from "../store/User/userSelector";
63
import { fetchUser } from "../store/User/userActions";
64
65
export function useUser(userId: number | undefined): User | null {
66
  return useSelector((state: RootState) =>
67
    userId ? getUserById(state, { userId }) : null,
68
  );
69
}
70
export function useApplication(
71
  applicationId: number,
72
): ApplicationNormalized | null {
73
  return useSelector((state: RootState) =>
74
    getApplicationNormalized(state, { applicationId }),
75
  );
76
}
77
78
export function useReviewedApplication(
79
  applicationId: number,
80
): Application | null {
81
  return useSelector((state: RootState) =>
82
    getApplicationById(state, { id: applicationId }),
83
  );
84
}
85
86
export function useJob(jobId: number | undefined): Job | null {
87
  return useSelector((state: RootState) =>
88
    jobId ? getJob(state, { jobId }) : null,
89
  );
90
}
91
92
export function useExperienceConstants(): {
93
  awardRecipientTypes: AwardRecipientType[];
94
  awardRecognitionTypes: AwardRecognitionType[];
95
  educationTypes: EducationType[];
96
  educationStatuses: EducationStatus[];
97
} {
98
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
99
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
100
  const educationTypes = useSelector(getEducationTypes);
101
  const educationStatuses = useSelector(getEducationStatuses);
102
  return {
103
    awardRecipientTypes,
104
    awardRecognitionTypes,
105
    educationTypes,
106
    educationStatuses,
107
  };
108
}
109
110
export function useSkills(): Skill[] {
111
  return useSelector(getSkills);
112
}
113
114
export function useCriteria(jobId: number | undefined): Criteria[] {
115
  return useSelector((state: RootState) =>
116
    jobId ? getCriteriaByJob(state, { jobId }) : [],
117
  );
118
}
119
120
export function useExperiences(
121
  applicationId: number,
122
  application: ApplicationNormalized | null,
123
): ExperienceType[] {
124
  const applicantId = application?.applicant_id ?? 0;
125
126
  // When an Application is still a draft, use Experiences associated with the applicant profile.
127
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
128
  const useProfileExperience =
129
    application === null ||
130
    application.application_status_id === ApplicationStatusId.draft;
131
132
  // This selector must be memoized because getExperienceByApplicant/Application uses reselect, and not re-reselect, so it needs to preserve its state.
133
  const experienceSelector = useCallback(
134
    (state: RootState) =>
135
      useProfileExperience
136
        ? getExperienceByApplicant(state, { applicantId })
137
        : getExperienceByApplication(state, { applicationId }),
138
    [applicationId, applicantId, useProfileExperience],
139
  );
140
  const experiencesByType = useSelector(experienceSelector);
141
  const experiences: ExperienceType[] = [
142
    ...experiencesByType.award,
143
    ...experiencesByType.community,
144
    ...experiencesByType.education,
145
    ...experiencesByType.personal,
146
    ...experiencesByType.work,
147
  ];
148
  return experiences;
149
}
150
151
export function useExperienceSkills(
152
  applicationId: number,
153
  application: ApplicationNormalized | null,
154
): ExperienceSkill[] {
155
  // ExperienceSkills don't need to be fetched because they are returned in the Experiences API calls.
156
  const applicantId = application?.applicant_id ?? 0;
157
  const useProfileExperience =
158
    application === null ||
159
    application.application_status_id === ApplicationStatusId.draft;
160
  const expSkillSelector = (state: RootState) =>
161
    useProfileExperience
162
      ? getExperienceSkillsByApplicant(state, { applicantId })
163
      : getExperienceSkillsByApplication(state, { applicationId });
164
  const experienceSkills = useSelector(expSkillSelector);
165
  return experienceSkills;
166
}
167
168
export function useJobPosterQuestions(
169
  jobId: number | undefined,
170
): JobPosterQuestion[] {
171
  return useSelector((state: RootState) =>
172
    jobId ? getJobPosterQuestionsByJob(state, { jobId }) : [],
173
  );
174
}
175
176
export function useJobApplicationAnswers(
177
  applicationId: number,
178
): JobApplicationAnswer[] {
179
  return useSelector((state: RootState) =>
180
    getJobApplicationAnswers(state, { applicationId }),
181
  );
182
}
183
184
export function useApplicationUser(applicationId: number): User | null {
185
  const application = useApplication(applicationId);
186
  const user = application?.applicant.user ?? null;
187
  return user;
188
}
189
190
/**
191
 * Return all skills from the redux store, and fetch the skills from backend if they are not yet in the store.
192
 * @param dispatch
193
 */
194
export function useFetchSkills(dispatch: DispatchType): Skill[] {
195
  const skills = useSelector(getSkills);
196
  const skillsUpdating = useSelector(getSkillsUpdating);
197
  useEffect(() => {
198
    if (skills.length === 0 && !skillsUpdating) {
199
      dispatch(fetchSkills());
200
    }
201
  }, [skills.length, skillsUpdating, dispatch]);
202
  return skills;
203
}
204
205
/**
206
 * Return all Experience constants from the redux store, and fetch them from backend if they are not yet in the store.
207
 * @param dispatch
208
 */
209
export function useFetchExperienceConstants(
210
  dispatch: DispatchType,
211
): {
212
  awardRecipientTypes: AwardRecipientType[];
213
  awardRecognitionTypes: AwardRecognitionType[];
214
  educationTypes: EducationType[];
215
  educationStatuses: EducationStatus[];
216
} {
217
  const awardRecipientTypes = useSelector(getAwardRecipientTypes);
218
  const awardRecipientTypesLoading = useSelector(
219
    (state: RootState) => state.awardRecipientType.loading,
220
  );
221
  useEffect(() => {
222
    if (awardRecipientTypes.length === 0 && !awardRecipientTypesLoading) {
223
      dispatch(fetchAwardRecipientTypes());
224
    }
225
  }, [awardRecipientTypes, awardRecipientTypesLoading, dispatch]);
226
227
  const awardRecognitionTypes = useSelector(getAwardRecognitionTypes);
228
  const awardRecognitionTypesLoading = useSelector(
229
    (state: RootState) => state.awardRecognitionType.loading,
230
  );
231
  useEffect(() => {
232
    if (awardRecognitionTypes.length === 0 && !awardRecognitionTypesLoading) {
233
      dispatch(fetchAwardRecognitionTypes());
234
    }
235
  }, [awardRecognitionTypes, awardRecognitionTypesLoading, dispatch]);
236
237
  const educationTypes = useSelector(getEducationTypes);
238
  const educationTypesLoading = useSelector(
239
    (state: RootState) => state.educationType.loading,
240
  );
241
  useEffect(() => {
242
    if (educationTypes.length === 0 && !educationTypesLoading) {
243
      dispatch(fetchEducationTypes());
244
    }
245
  }, [educationTypes, educationTypesLoading, dispatch]);
246
247
  const educationStatuses = useSelector(getEducationStatuses);
248
  const educationStatusesLoading = useSelector(
249
    (state: RootState) => state.educationStatus.loading,
250
  );
251
  useEffect(() => {
252
    if (educationStatuses.length === 0 && !educationStatusesLoading) {
253
      dispatch(fetchEducationStatuses());
254
    }
255
  }, [educationStatuses, educationStatusesLoading, dispatch]);
256
257
  return {
258
    awardRecipientTypes,
259
    awardRecognitionTypes,
260
    educationTypes,
261
    educationStatuses,
262
  };
263
}
264
265
/**
266
 * Return an Application (normalized, ie without Review) from the redux store, and fetch it from backend if it is not yet in the store.
267
 * @param applicationId
268
 * @param dispatch
269
 */
270
export function useFetchNormalizedApplication(
271
  applicationId: number,
272
  dispatch: DispatchType,
273
): ApplicationNormalized | null {
274
  const applicationSelector = (
275
    state: RootState,
276
  ): ApplicationNormalized | null =>
277
    getApplicationNormalized(state, { applicationId });
278
  const application: ApplicationNormalized | null = useSelector(
279
    applicationSelector,
280
  );
281
  const applicationIsUpdating = useSelector((state: RootState) =>
282
    getApplicationIsUpdating(state, { applicationId }),
283
  );
284
  const [applicationFetched, setApplicationFetched] = useState(false);
285
  useEffect(() => {
286
    if (application === null && !applicationIsUpdating && !applicationFetched) {
287
      setApplicationFetched(true);
288
      dispatch(fetchApplicationNormalized(applicationId));
289
    }
290
  }, [application, applicationId, applicationIsUpdating, dispatch]);
291
  return application;
292
}
293
294
/**
295
 * Return an Application from the redux store, and fetch it from backend if it is not yet in the store.
296
 * @param applicationId
297
 * @param dispatch
298
 */
299
export function useFetchApplication(
300
  applicationId: number,
301
  dispatch: DispatchType,
302
): Application | null {
303
  const applicationSelector = (state: RootState): Application | null =>
304
    getApplicationById(state, { id: applicationId });
305
  const application: Application | null = useSelector(applicationSelector);
306
  const applicationIsUpdating = useSelector((state: RootState) =>
307
    getApplicationIsUpdating(state, { applicationId }),
308
  );
309
  useEffect(() => {
310
    if (application === null && !applicationIsUpdating) {
311
      dispatch(fetchApplication(applicationId));
312
    }
313
  }, [application, applicationId, applicationIsUpdating, dispatch]);
314
  return application;
315
}
316
317
/**
318
 * Return an Job from the redux store, and fetch it from backend if it is not yet in the store.
319
 * @param jobId
320
 * @param dispatch
321
 */
322
export function useFetchJob(
323
  jobId: number | undefined,
324
  dispatch: DispatchType,
325
): Job | null {
326
  const job = useJob(jobId);
327
  const jobUpdatingSelector = (state: RootState) =>
328
    jobId ? getJobIsUpdating(state, jobId) : false;
329
  const jobIsUpdating = useSelector(jobUpdatingSelector);
330
  useEffect(() => {
331
    // If job is null and not already updating, fetch it.
332
    if (jobId && job === null && !jobIsUpdating) {
333
      dispatch(fetchJob(jobId));
334
    }
335
  }, [jobId, job, jobIsUpdating, dispatch]);
336
  return job;
337
}
338
339
/**
340
 * Return all Experience relavant to an Application from the redux store, and fetch it from backend if it is not yet in the store.
341
 * @param applicationId
342
 * @param application
343
 * @param dispatch
344
 */
345
export function useFetchExperience(
346
  applicationId: number,
347
  application: ApplicationNormalized | null,
348
  dispatch: DispatchType,
349
): {
350
  experiences: ExperienceType[];
351
  experiencesUpdating: boolean;
352
  experiencesFetched: boolean;
353
} {
354
  const applicantId = application?.applicant_id ?? 0;
355
356
  // When an Application is still a draft, use Experiences associated with the applicant profile.
357
  // When an Application has been submitted and is no longer a draft, display Experience associated with the Application directly.
358
  const applicationLoaded = application !== null;
359
  const useProfileExperience =
360
    application === null ||
361
    application.application_status_id === ApplicationStatusId.draft;
362
363
  const experiences = useExperiences(applicationId, application);
364
  const experiencesUpdating = useSelector((state: RootState) =>
365
    useProfileExperience
366
      ? getUpdatingByApplicant(state, { applicantId })
367
      : getUpdatingByApplication(state, { applicationId }),
368
  );
369
  const [experiencesFetched, setExperiencesFetched] = useState(false);
370
  useEffect(() => {
371
    // Only load experiences if they have never been fetched by this component (!experiencesFetched),
372
    //  have never been fetched by another component (length === 0),
373
    //  and are not currently being fetched (!experiencesUpdating).
374
    // Also, wait until application has been loaded so the correct source can be determined.
375
    if (
376
      applicationLoaded &&
377
      !experiencesFetched &&
378
      !experiencesUpdating &&
379
      experiences.length === 0
380
    ) {
381
      setExperiencesFetched(true);
382
      if (useProfileExperience) {
383
        dispatch(fetchExperienceByApplicant(applicantId));
384
      } else {
385
        dispatch(fetchExperienceByApplication(applicationId));
386
      }
387
    }
388
  }, [
389
    applicantId,
390
    applicationId,
391
    applicationLoaded,
392
    dispatch,
393
    experiences.length,
394
    experiencesFetched,
395
    experiencesUpdating,
396
    useProfileExperience,
397
  ]);
398
  return {
399
    experiences,
400
    experiencesUpdating,
401
    experiencesFetched,
402
  };
403
}
404
405
/**
406
 * Return an User from the redux store, and fetch it from backend if it is not yet in the store.
407
 * @param jobId
408
 * @param dispatch
409
 */
410
export function useFetchUser(
411
  userId: number,
412
  dispatch: DispatchType,
413
): User | null {
414
  const user = useUser(userId);
415
  useEffect(() => {
416
    // If job is null and not already updating, fetch it.
417
    if (userId) {
418
      dispatch(fetchUser(userId));
419
    }
420
  }, [userId, dispatch]);
421
  return user;
422
}
423
424
/**
425
 * Trigger fetches for all data needed for the Application process which is not yet in the redux store, or in the process of loading.
426
 * @param applicationId
427
 */
428
export function useFetchAllApplicationData(
429
  applicationId: number,
430
  dispatch: DispatchType,
431
): {
432
  applicationLoaded: boolean;
433
  userLoaded: boolean;
434
  jobLoaded: boolean;
435
  criteriaLoaded: boolean;
436
  experiencesLoaded: boolean;
437
  experienceSkillsLoaded: boolean;
438
  jobQuestionsLoaded: boolean;
439
  applicationAnswersLoaded: boolean;
440
  experienceConstantsLoaded: boolean;
441
  skillsLoaded: boolean;
442
} {
443
  const application = useFetchNormalizedApplication(applicationId, dispatch);
444
  const jobId = application?.job_poster_id;
445
  const job = useFetchJob(jobId, dispatch);
446
  const { experiences, experiencesUpdating } = useFetchExperience(
447
    applicationId,
448
    application,
449
    dispatch,
450
  );
451
  const {
452
    awardRecipientTypes,
453
    awardRecognitionTypes,
454
    educationTypes,
455
    educationStatuses,
456
  } = useFetchExperienceConstants(dispatch);
457
  const skills = useFetchSkills(dispatch);
458
459
  const applicationLoaded = application !== null;
460
  const jobLoaded = job !== null;
461
  const experiencesLoaded = !experiencesUpdating || experiences.length > 0;
462
  const experienceConstantsLoaded =
463
    awardRecipientTypes.length > 0 &&
464
    awardRecognitionTypes.length > 0 &&
465
    educationTypes.length > 0 &&
466
    educationStatuses.length > 0;
467
  const skillsLoaded = skills.length > 0;
468
469
  return {
470
    applicationLoaded,
471
    jobLoaded,
472
    experiencesLoaded,
473
    experienceConstantsLoaded,
474
    skillsLoaded,
475
    criteriaLoaded: jobLoaded,
476
    experienceSkillsLoaded: experiencesLoaded,
477
    jobQuestionsLoaded: jobLoaded,
478
    applicationAnswersLoaded: applicationLoaded,
479
    userLoaded: applicationLoaded,
480
  };
481
}
482
483
/**
484
 * Trigger fetches for all data needed for the Application review process which is not yet in the redux store, or in the process of loading.
485
 * @param applicationId
486
 */
487
export function useFetchReviewApplicationData(
488
  applicantUserId: number,
489
  applicationId: number,
490
  jobId: number,
491
  dispatch: DispatchType,
492
): {
493
  applicationLoaded: boolean;
494
  jobLoaded: boolean;
495
  experiencesLoaded: boolean;
496
  experienceConstantsLoaded: boolean;
497
  skillsLoaded: boolean;
498
} {
499
  const application = useFetchApplication(applicationId, dispatch);
500
  const job = useFetchJob(jobId, dispatch);
501
  const { experiences, experiencesUpdating } = useFetchExperience(
502
    applicationId,
503
    application,
504
    dispatch,
505
  );
506
  const {
507
    awardRecipientTypes,
508
    awardRecognitionTypes,
509
    educationTypes,
510
    educationStatuses,
511
  } = useFetchExperienceConstants(dispatch);
512
  const skills = useFetchSkills(dispatch);
513
514
  return {
515
    applicationLoaded: application !== null,
516
    jobLoaded: job !== null,
517
    experiencesLoaded: !experiencesUpdating || experiences.length > 0,
518
    experienceConstantsLoaded:
519
      awardRecipientTypes.length > 0 &&
520
      awardRecognitionTypes.length > 0 &&
521
      educationTypes.length > 0 &&
522
      educationStatuses.length > 0,
523
    skillsLoaded: skills.length > 0,
524
  };
525
}
526